import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JPanel;

import net.sf.stackwrap4j.entities.User;

import org.joda.time.DateTime;

public class GraphPanel extends JPanel {

    private final int Edge_Padding = 16;

    private ReputationGraph repGrap;

    private int minRep = 0, maxRep = Integer.MIN_VALUE;
    private DateTime minDate = new DateTime(2060, 1, 1, 1, 1, 1, 1), maxDate = new DateTime(0);

    private List<UserColorPair> users;
    private Map<String, List<RepPoint>> idToRepPoints;

    public GraphPanel(ReputationGraph parent) {
        repGrap = parent;
        users = new ArrayList<UserColorPair>();
        idToRepPoints = new HashMap<String, List<RepPoint>>();
        setBackground(Color.WHITE);
        setToolTipText("");
        setPreferredSize(new Dimension(400, 400));
        addMouseListener(clickListener);
        addMouseMotionListener(moveListener);
    }

    public void addUser(User u, String site, List<RepPoint> reps, Color c) {
        users.add(new UserColorPair(u, site, c));
        idToRepPoints.put(site + ":" + u.getId(), reps);
        repaint();
        int repSum = 0;
        for (RepPoint r : reps) {
            repSum += r.getRepToday();
            minRep = Math.min(minRep, repSum);
            maxRep = Math.max(maxRep, repSum);
            minDate = minDate.isBefore(r.getDate()) ? minDate : r.getDate();
            maxDate = maxDate.isBefore(r.getDate()) ? r.getDate() : maxDate;
        }
    }

    public void removeUser(User u, String site) {
        for (int i = 0; i < users.size(); i++) {
            User u2 = users.get(i).user;
            String site2 = users.get(i).site;
            if ((u2.getId() == u.getId()) && (site.equals(site2))) {
                users.remove(i);
                repaint();
                return;
            }
        }
        idToRepPoints.remove(site + ":" + u.getId());
    }

    public DateTime[] getSelectedRange() {
        DateTime[] ret = new DateTime[2];
        ret[0] = getStartRange();
        ret[1] = getEndRange();
        return ret;
    }

    @Override
    public String getToolTipText(MouseEvent event) {
        return translateXValToDate(event.getX()).toString("MMM dd yyyy");
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        paintRule(g2);
        for (UserColorPair u_c : users) {
            g2.setColor(u_c.color);
            List<RepPoint> points = idToRepPoints.get(u_c.site + ":" + u_c.user.getId());
            if (points == null)
                continue;
            int totalRep = 0;
            RepPoint first = null;
            int lastTotalRep = -1;
            for (RepPoint p : points) {
                lastTotalRep = totalRep;
                totalRep += p.getRepToday();
                paintPoint(p, totalRep, g2);
                paintLine(first, p, lastTotalRep, totalRep, g2);
                first = p;
            }
        }
        drawRange(g2);
    }

    private void paintLine(RepPoint first, RepPoint p, int totalRep1, int totalRep2, Graphics2D g2) {
        if (first == null)return;
        int y1 = translateRepToYVal(totalRep1);
        int x1 = translateDateToXVal(first.getDate());
        int y2 = translateRepToYVal(totalRep2);
        int x2 = translateDateToXVal(p.getDate());
        g2.drawLine(x1, y1, x2, y2);
    }

    private void paintPoint(RepPoint p, int totalRep, Graphics2D g2) {
        final int width = 6;
        final int height = 6;
        int y = translateRepToYVal(totalRep);
        int x = translateDateToXVal(p.getDate());
        g2.fillOval(x - width / 2, y - height / 2, width, height);
    }

    private void paintRule(Graphics2D g2) {
        Color saveColor = g2.getColor();
        Stroke saveStroke = g2.getStroke();
        paintXRule(g2);
        paintYRule(g2);
        g2.setStroke(saveStroke);
        g2.setColor(saveColor);
    }

    private void paintYRule(Graphics2D g2) {
        Color saveColor = g2.getColor();
        Stroke saveStroke = g2.getStroke();
        g2.setColor(Color.LIGHT_GRAY);
        int repIncrement = calculateRepIncrement();
        for (int i = minRep; i <= maxRep; i += repIncrement) {
            int y = translateRepToYVal(i);
            g2.setColor(Color.BLACK);
            g2.drawString("" + i, Edge_Padding / 2, y);
            g2.setColor(Color.LIGHT_GRAY);
            g2.drawLine(Edge_Padding, y, getWidth() - Edge_Padding / 2, y);
        }
        g2.setStroke(saveStroke);
        g2.setColor(saveColor);
    }

    private void paintXRule(Graphics2D g2) {
        Color saveColor = g2.getColor();
        Stroke saveStroke = g2.getStroke();
        g2.setColor(Color.LIGHT_GRAY);
        long dateIncrement = calculateDateIncrement();
        for (long i = minDate.getMillis(); i <= maxDate.getMillis(); i += dateIncrement) {
            int x = translateDateToXVal(new DateTime(i));
            g2.setColor(Color.BLACK);
            g2.drawString(new DateTime(i).toString("MMM dd YY"), x, getHeight()
                    - ((Edge_Padding / 2) - 3));
            g2.setColor(Color.LIGHT_GRAY);
            g2.drawLine(x, Edge_Padding, x, getHeight() - Edge_Padding);
        }
        g2.setStroke(saveStroke);
        g2.setColor(saveColor);
    }

    private void drawRange(Graphics2D g) {
        int transparency = 75;
        if (clickedPoint == null || upPoint == null)
            return;
        Color red = Color.RED;
        g.setColor(new Color(red.getRed(), red.getGreen(), red.getBlue(), transparency));
        int width = Math.abs(upPoint.x - clickedPoint.x);
        g.fillRect(Math.min(clickedPoint.x, upPoint.x), 0, width, getHeight());
    }

    private long calculateDateIncrement() {
        long dateDiff = maxDate.getMillis() - minDate.getMillis();
        final double desiredNumberOfLines = 10.0;
        double dateIncrement = dateDiff / desiredNumberOfLines;
        return (long) dateIncrement;
    }

    int translateRepToYVal(int rep) {
        int height = getHeight() - Edge_Padding * 2;
        int repDiff = maxRep - minRep;
        double scale = height / ((double) repDiff);
        int shiftedRep = minRep < 0 ? rep - minRep : rep;
        return (height - (int) (shiftedRep * scale)) + Edge_Padding;
    }

    int translateDateToXVal(DateTime date) {
        long dateDiff = maxDate.getMillis() - minDate.getMillis();
        int width = getWidth() - (Edge_Padding * 2);
        double scale = width / ((double) dateDiff);
        long shiftedDate = date.getMillis() - minDate.getMillis();
        int translatedVal = (int) Math.round(shiftedDate * scale);
        return translatedVal + Edge_Padding;
    }

    DateTime translateXValToDate(int xVal) {
        xVal = xVal - Edge_Padding;
        double percent = xVal / (double) (getWidth() - (Edge_Padding * 2));
        long dateRange = maxDate.getMillis() - minDate.getMillis();
        long dateInMilis = (long) ((dateRange * percent) + minDate.getMillis());
        return new DateTime(dateInMilis);
    }

    private int calculateRepIncrement() {
        int yDiff = maxRep - minRep;
        final double desiredNumberOfLines = 10.0;
        double repIncrement = yDiff / desiredNumberOfLines;
        if (repIncrement > 10000)
            return 20000;
        if (repIncrement > 5000)
            return 10000;
        if (repIncrement > 2500)
            return 5000;
        if (repIncrement > 1000)
            return 2500;
        if (repIncrement > 500)
            return 1000;
        if (repIncrement > 250)
            return 500;
        if (repIncrement > 100)
            return 250;
        if (repIncrement > 50)
            return 100;
        if (repIncrement > 25)
            return 50;
        return 10;
    }

    public DateTime getStartRange() {
        if ((clickedPoint == null || upPoint == null) || (clickedPoint.x - upPoint.x == 0))
            return null;
        int xStart = Math.min(clickedPoint.x, upPoint.x);
        DateTime startDate = translateXValToDate(xStart);
        return startDate;
    }

    public DateTime getEndRange() {
        if ((clickedPoint == null || upPoint == null) || (clickedPoint.x - upPoint.x == 0))
            return null;
        int xEnd = Math.min(clickedPoint.x, upPoint.x) + Math.abs(clickedPoint.x - upPoint.x);
        DateTime endDate = translateXValToDate(xEnd);
        return endDate;
    }

    private Point clickedPoint;
    private Point upPoint;

    private MouseMotionListener moveListener = new MouseMotionAdapter() {

        @Override
        public void mouseDragged(MouseEvent e) {
            upPoint = e.getPoint();
            repaint();
        }
    };

    public static final String REP_SELECTION_CHANGE = "MmmmmBeer!!!";

    private MouseListener clickListener = new MouseAdapter() {

        @Override
        public void mouseReleased(MouseEvent e) {
            repGrap.dateRangeChanged();
            repaint();
        }

        @Override
        public void mousePressed(MouseEvent e) {
            clickedPoint = e.getPoint();
            upPoint = null;
            repaint();
        }
    };
}
